import axios from "axios";
import * as crypto from 'crypto';
import WechatOpen from "./open";

/**
 * 签名
 * @param params 待签名参数
 */
function sign(params: SignParams) {
    var ss = [];
    for (let k in params) {
        let v = params[k];
        ss.push([k, v]);
    }
    ss.sort((a, b) => (a > b ? 1 : -1));
    return crypto.createHash('sha1').update(ss.map(x => x[0].toLowerCase() + "=" + x[1]).join("&")).digest('hex');
}

function timestamp() {
    let t = new Date()
    let year = t.getFullYear().toString();
    var month = (t.getMonth() + 1).toString();
    if (month.length < 2) month = "0" + month;
    var date = t.getDate().toString();
    if (date.length < 2) date = "0" + date;
    var hours = t.getHours().toString();
    if (hours.length < 2) hours = "0" + hours;
    var mintues = t.getMinutes().toString();
    if (mintues.length < 2) mintues = "0" + mintues;
    var seconds = t.getSeconds().toString();
    if (seconds.length < 2) seconds = "0" + seconds;
    return `${year}-${month}-${date} ${hours}:${mintues}:${seconds}`
}

interface SignParams {
    nonceStr: string;
    jsapi_ticket: string;
    timestamp: number;
    url: string;
}

export interface SingData extends SignParams {
    signature: string;
    appId: string;
}

export interface OAuth2Response {
    access_token: string;
    expires_in: number;
    refresh_token: string;
    openid: string;
    scope: string;
}

export interface SnsUserInfoResponse {
    openid: string;
    nickname: string;
    sex: string;
    province: string;
    city: string;
    country: string;
    headimgurl: string;
    privilege: string[];
    unionid: string;
    language?: string;
}

export interface RefreshTokenResponse {
    access_token: string;
    expires_in: number;
    refresh_token: string;
    openid: string;
    scope: string;
}

interface Menu {
    button: Buttons | Button[];
}

interface Buttons {
    name: string;
    sub_button: Button[];
}

type Button = ClickButton | ViewButton | MicroButton;

interface ClickButton {
    type: 'click';
    name: string;
    key: string;
}

interface ViewButton {
    type: 'view';
    name: string;
    url: string;
}

interface MicroButton {
    type: 'miniprogram';
    name: string;
    url: string;
    appid: string;
    pagepath: string;
}

interface SendTemplateBody {
    touser: string;
    template_id: string;
    url?: string;
    miniprogram?: Miniprogram;
    data: { [keyword: string]: string };
}

interface Miniprogram {
    appid: string;
    pagepath: string;
}

interface APIResponse {
    errcode: number;
    errmsg: string;
}

interface QRCodeResponse {
    ticket: string;
    expire_seconds: number;
    url: string;
}

interface SendVideoBody {
    media_id: string;
    thumb_media_id: string;
    title?: string;
    description?: string;
}

interface SendMusicBody {
    title?: string;
    description?: string;
    musicurl: string;
    hqmusicurl: string;
    thumb_media_id: string;
}

interface Article {
    title: string;
    description: string;
    url: string;
    picurl: string;
}

interface SendMenuBody {
    head_content: string;
    list: {
        id: string;
        content: string;
    }[];
    tail_content: string;
}

interface SendMiniBody {
    title: string;
    appid: string;
    pagepath: string;
    thumb_media_id: string;
}

interface UserInfo {
    subscribe: number;
    openid: string;
    nickname: string;
    sex: number;
    language: string;
    city: string;
    province: string;
    country: string;
    headimgurl: string;
    subscribe_time: number;
    unionid: string;
    remark: string;
    groupid: number;
    tagid_list: number[];
    subscribe_scene: string;
    qr_scene: number;
    qr_scene_str: string;
}

export const SUBSCRIBE_SCENES = [
    "ADD_SCENE_SEARCH", // 公众号搜索
    "ADD_SCENE_ACCOUNT_MIGRATION", // 公众号迁移
    "ADD_SCENE_PROFILE_CARD", // 名片分享
    "ADD_SCENE_QR_CODE", // 扫描二维码
    "ADD_SCENE_PROFILE_LINK", // 图文页内名称点击
    "ADD_SCENE_PROFILE_ITEM", // 图文页右上角菜单
    "ADD_SCENE_PAID", // 支付后关注
    "ADD_SCENE_OTHERS", // 其他
];

interface OpenIDs {
    total: number;
    count: number;
    data: { openid: string[] };
    next_openid: string;
}

export default class WechatMp extends WechatOpen {
    private access_token: string;
    private access_token_expired_at: number;
    private jsapi_ticket: string;
    private jsapi_ticket_expired_at: number;
    protected saveAccessToken(access_token: string, expired_at: number) {
        this.access_token = access_token;
        this.access_token_expired_at = expired_at;
    }
    protected loadAccessToken(): string {
        return this.access_token_expired_at > Date.now() ? this.access_token : '';
    }
    protected saveJSApiTicket(jsapi_ticket: string, expired_at: number) {
        this.jsapi_ticket = jsapi_ticket;
        this.jsapi_ticket_expired_at = expired_at;
    }
    protected loadJSApiTicket(): string {
        return this.jsapi_ticket_expired_at > Date.now() ? this.jsapi_ticket : '';
    }
    async getAccessToken(): Promise<string> {
        let token = await this.loadAccessToken();
        if (token) return token;
        let { data } = await axios.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.appid}&secret=${this.secret}`);
        if (data.errcode) throw data;
        this.saveAccessToken(data.access_token, Date.now() + data.expires_in * 1e3 - 5e3);
        return data.access_token;
    }
    async getJsapiTicket(): Promise<string> {
        let ticket = await this.loadJSApiTicket();
        if (ticket) return ticket;
        let token = await this.getAccessToken();
        let { data } = await axios.get(`https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=jsapi`);
        if (data.errcode) throw data;
        this.jsapi_ticket = data.ticket;
        this.saveAccessToken(data.ticket, Date.now() + data.expires_in * 1e3 - 5e3);
        return this.jsapi_ticket;
    }
    /**
     * 获取jssdk配置
     */
    async jssdkConfig(url: string): Promise<SingData> {
        let nonceStr = '';
        nonceStr += Math.random().toString(36).slice(2)
        nonceStr += Math.random().toString(36).slice(2)
        nonceStr = nonceStr.slice(0, 16);
        let data = {
            nonceStr, // 必填，生成签名的随机串
            jsapi_ticket: await this.getJsapiTicket(),
            timestamp: Math.floor(Date.now() / 1e3), // 必填，生成签名的时间戳
            url,
        };
        let signature = sign(data);
        delete data.jsapi_ticket;
        let appId = this.appid;
        return Object.assign(data, { signature, appId });
    }

    async sendTemplate(body: SendTemplateBody): Promise<APIResponse> {
        let url = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=` + await this.getAccessToken()
        let data = {};
        for (let k in body.data) {
            let v = body.data[k]
            data[k] = { value: v }
        }
        let ret = await axios.post(url, Object.assign(body, { data }))
        return ret.data;
    }

    sendNotice(open_id: string, title: string, content: string, url?: string, remark?: string) {
        return this.sendTemplate({
            touser: open_id,
            template_id: 'nfjZ4oVwIQeIJ1WCStSm0pl63YF7uc0PWcB4xAkZJPg',
            url: url,
            data: {
                first: title,
                keyword1: content,
                keyword2: timestamp(),
                remark: remark,
            },
        })
    }

    async send(data: any) {
        let url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=` + await this.getAccessToken();
        let ret = await axios.post(url, data)
        return ret.data;
    }

    sendText(openid: string, text: string) {
        if (!text) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "text",
            "text": {
                "content": text
            }
        })
    }

    sendImage(openid: string, media_id: string) {
        if (!media_id) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "image",
            "image": {
                media_id
            }
        })
    }

    sendVoice(openid: string, media_id: string) {
        if (!media_id) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "voice",
            "voice": {
                media_id
            }
        })
    }

    sendVideo(openid: string, body: SendVideoBody) {
        if (!body) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "video",
            "video": body
        })
    }

    sendNews(openid: string, article: Article | string) {
        if (!article) return Promise.resolve()
        if (typeof article === "string")
            return this.send({
                "touser": openid,
                "msgtype": "mpnews",
                "mpnews": { media_id: article }
            })
        return this.send({
            "touser": openid,
            "msgtype": "news",
            "news": { articles: [article] }
        })
    }

    sendMusic(openid: string, body: SendMusicBody) {
        if (!body) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "music",
            "music": body
        })
    }

    sendMenu(openid: string, body: SendMenuBody) {
        if (!body) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "msgmenu",
            "msgmenu": body
        })
    }

    sendCard(openid: string, card_id: string) {
        if (!card_id) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "wxcard",
            "wxcard": {
                card_id
            }
        })
    }

    sendMini(openid: string, body: SendMiniBody) {
        if (!body) return Promise.resolve()
        return this.send({
            "touser": openid,
            "msgtype": "miniprogrampage",
            "miniprogrampage": body
        })
    }

    async getUserInfo(open_id: string, lang?: string): Promise<UserInfo> {
        lang = lang || "zh_CN";
        let url = `https://api.weixin.qq.com/cgi-bin/user/info?openid=${open_id}&lang=${lang}&access_token=` + await this.getAccessToken();
        let ret = await axios.get(url)
        return ret.data;
    }

    async getOpenIDs(next_openid?: string): Promise<OpenIDs> {
        next_openid = next_openid || ""
        let url = `https://api.weixin.qq.com/cgi-bin/user/get?next_openid=${next_openid}&access_token=` + await this.getAccessToken();
        let ret = await axios.get(url)
        return ret.data;
    }

    async qrcodeCreate(data: number | string, expire_seconds?: number): Promise<QRCodeResponse> {
        if (expire_seconds) expire_seconds = Math.max(Math.min(expire_seconds, 2592000), 0)
        let url = `https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=` + await this.getAccessToken();
        // 二维码类型，QR_SCENE为临时的整型参数值，QR_STR_SCENE为临时的字符串参数值，QR_LIMIT_SCENE为永久的整型参数值，QR_LIMIT_STR_SCENE为永久的字符串参数值
        let action_name = "QR";
        let scene: { scene_str: string } | { scene_id: number }
        if (!expire_seconds) {
            action_name += "_LIMIT"
        }
        if (typeof data === "string") {
            action_name += "_STR"
            scene = { scene_str: data }
        } else {
            scene = { scene_id: data }
        }
        action_name += "_SCENE"
        let ret = await axios.post(url, { expire_seconds, action_name, action_info: { scene } })
        return ret.data;
    }

    /**
     * 创建菜单
     * https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
     * @param data 
     */
    async menuCreate(data: Menu): Promise<APIResponse> {
        let url = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=` + await this.getAccessToken();
        let ret = await axios.post(url, data)
        return ret.data;
    }
}